package com.tianji.promotion.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.ObjectUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.IncrUsedCoupon;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.UserCouponQuery;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.utils.CodeUtil;
import com.tianji.promotion.utils.UserCouponLock;
import lombok.RequiredArgsConstructor;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author LYQ
 * @since 2024-07-06
 */
@Service
@RequiredArgsConstructor
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {
    private final CouponMapper couponMapper;
    private final IExchangeCodeService codeService;

    /**
     * 用户领取优惠券
     *
     * @param id
     */
    @Override
    public void addCoupon2User(Long id) {
        //1、查询优惠券是否存在
        Coupon coupon = couponMapper.selectById(id);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在！！");
        }
        //2、查询优惠券状态是否为发放中
        if (coupon.getStatus() != CouponStatus.ISSUING) {
            throw new BadRequestException("优惠券未开始方法或发已结束");
        }
        //3、查询优惠券当前已发放数量
        if (coupon.getIssueNum() >= coupon.getTotalNum()) {
            throw new BadRequestException("优惠券库存不足！！！");
        }
        //4、校验用户是否超领，并修改优惠券已发放数量，同时生成用户券信息存入user_coupon表中
        synchronized (UserContext.getUser().toString().intern()) {
            IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
            userCouponService.checkAndCreateUserCoupon(coupon, null);
        }
    }


    /**
     * 新增用户券
     *
     * @param coupon
     * @param userId
     */
    public void insertUserCoupon(Coupon coupon, Long userId) {
        UserCoupon us = new UserCoupon();
        //1、填入优惠券id和用户id
        us.setCouponId(coupon.getId());
        us.setUserId(userId);
        //2、判断优惠券是否有有效天数
        if (coupon.getTermDays() != 0) {
            //存在有效期，则有效期为当前时间，有效期结束为当前时间加上有效天数
            us.setTermBeginTime(LocalDateTime.now());
            us.setTermEndTime(LocalDateTime.now().plusDays(coupon.getTermDays()));
        } else {
            //不存在有效期，则有效期范围为优惠券的有效期
            us.setTermBeginTime(coupon.getTermBeginTime());
            us.setTermEndTime(coupon.getTermEndTime());
        }
        save(us);
    }


    /**
     * 兑换码兑换优惠券接口
     *
     * @param code
     */
    @Override
    public void exchangeCoupon(String code) {
        //1、解析兑换码、获取兑换码序列号
        long serialNum = CodeUtil.parseCode(code);
        // 2.校验是否已经兑换 ，直接执行setbit，通过返回值来判断是否兑换过
        boolean exchanged = codeService.updateExchangeMark(serialNum, true);
        if (exchanged) {
            //2.1、如果已使用，则抛出异常
            throw new BizIllegalException("兑换码已被使用");
        }
        try {
            //3、查询兑换码是否还存在于兑换码表中
            ExchangeCode exchangeCode = codeService.getById(serialNum);
            if (ObjectUtils.isEmpty(exchangeCode)) {
                throw new BizIllegalException("兑换码不存在");
            }
            //4、查询兑换码是否已经过期
            if (LocalDateTime.now().isAfter(exchangeCode.getExpiredTime())) {
                throw new BizIllegalException("兑换码已过期");
            }
            //5、查询目标优惠券
            Long couponId = exchangeCode.getExchangeTargetId();
            Coupon coupon = couponMapper.selectById(couponId);
            //6、校验并生成用户券，同时修改兑换码状态
            synchronized (UserContext.getUser().toString().intern()) {
                IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
                userCouponService.checkAndCreateUserCoupon(coupon, null);
            }
        } catch (BizIllegalException e) {
            //重置为未兑换
            codeService.updateExchangeMark(serialNum, false);
            throw e;
        }
    }


    /**
     * 校验用户是否超领，并修改优惠券已发放数量，同时生成用户券信息存入user_coupon表中
     *
     * @param coupon , serialNum
     */
    @Override
    @Transactional
    @UserCouponLock(name = "lock:coupon")
    public void checkAndCreateUserCoupon(Coupon coupon, Long serialNum) {
        //1、判断用户领取数量是否已经超过限领数量
        Long userId = UserContext.getUser();
        Integer count = lambdaQuery()
                .eq(UserCoupon::getCouponId, coupon.getId())
                .eq(UserCoupon::getUserId, userId)
                .count();
        if (count != null && count >= coupon.getUserLimit()) {
            throw new BadRequestException("优惠券限领数量已满！！！");
        }
        //2、优惠券已发放数量+1
        int r = couponMapper.updateIssueNum(coupon.getId(), coupon.getTotalNum());
        if (r == 0) {
            throw new BizIllegalException("慢了一步，优惠券已被领取完了~~");
        }
        //3、新增user-coupon表数据
        insertUserCoupon(coupon, userId);
        //4、修改兑换券表数据为已兑换
        if (serialNum != null) {
            codeService.lambdaUpdate()
                    .set(ExchangeCode::getUserId, UserContext.getUser())
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                    .eq(ExchangeCode::getId, serialNum)
                    .update();
        }
    }

    /**
     * 用户分页查询我的优惠券接口
     *
     * @param query
     */
    @Override
    public PageDTO<CouponVO> queryMyCouponsByPage(UserCouponQuery query) {
        Integer status = query.getStatus();
        //1、分页搜索
        Page<UserCoupon> pageResult = lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .eq(status != null, UserCoupon::getStatus, status)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<UserCoupon> userCoupons = pageResult.getRecords();
        if (ObjectUtils.isEmpty(userCoupons)) {
            return PageDTO.empty(pageResult);
        }
        //2、搜索对应的优惠券
        Set<Long> couponIds = userCoupons.stream().map(UserCoupon::getCouponId).collect(Collectors.toSet());
        List<Coupon> coupons = couponMapper.selectBatchIds(couponIds);
        Map<Long, Coupon> couponMap = new HashMap<>(coupons.size());
        if (ObjectUtils.isNotEmpty(coupons)) {
            couponMap = coupons.stream().collect(Collectors.toMap(Coupon::getId, Function.identity()));
        }
        //3、遍历用户的优惠券数据，一一转为vo
        List<CouponVO> list = new ArrayList<>(userCoupons.size());
        for (UserCoupon userCoupon : userCoupons) {
            //由于用户同一种优惠券可能有多张，每张优惠券的时间不同，以user_coupon表的有效时期为标准
            Long couponId = userCoupon.getCouponId();
            Coupon coupon = couponMap.get(couponId);
            if (coupon != null) {
                CouponVO couponVO = BeanUtils.copyBean(coupon, CouponVO.class);
                //由于生成用户券的时候会校验生成优惠券的有效时期，所以将优惠券的有效天数设为空，只留下有效截止日期即可
                couponVO.setTermDays(null);
                couponVO.setTermEndTime(userCoupon.getTermEndTime());
                list.add(couponVO);
            }
        }
        return PageDTO.of(pageResult, list);
    }

    /**
     * 核销优惠券
     *
     * @param userCouponIds
     */
    @Override
    @Transactional
    public void useUserCoupon(List<Long> userCouponIds) {
        //1、查询用户券
        List<UserCoupon> userCoupons = lambdaQuery()
                .in(UserCoupon::getId, userCouponIds)
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .list();
        //2、理论上前面的校验已经确保了userCouponIds中的优惠券id都是该用户拥有的并且可使用的，
        //  使用流map处理前进行一次过滤
        List<UserCoupon> updateList = userCoupons.stream().filter(userCoupon -> {
            if (userCoupon.getStatus() != UserCouponStatus.UNUSED) {
                return false;
            }
            LocalDateTime now = LocalDateTime.now();
            return !now.isBefore(userCoupon.getTermBeginTime()) && !now.isAfter(userCoupon.getTermEndTime());
        }).map(
                userCoupon ->
                        new UserCoupon()
                                .setId(userCoupon.getId())
                                .setStatus(UserCouponStatus.USED)
                                .setUsedTime(LocalDateTime.now())
        ).collect(Collectors.toList());
        //3、更新用户券状态
        int updateNum = updateList.size();
        boolean update;
        if (updateNum == 0) {
            return;
        } else if (updateNum == 1) {
            update = updateById(updateList.get(0));
        } else {
            update = updateBatchById(updateList);
        }
        if (!update) {
            throw new DbException("用户优惠券核销失败");
        }
        //4、更新优惠券
        //4.1获取用户券对应的coupon_id(由于可能有重复的，于是根据couponId进行分组)
        Map<Long, Long> couponCountMap = updateList.stream().collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        List<Long> couponIds = new ArrayList<>(couponCountMap.keySet());
        int num = 0;
        for (Long couponId : couponIds) {
            num += couponMapper.incrUsedNum(couponId, couponCountMap.get(couponId));
        }
        if (num != couponCountMap.size()) {
            throw new DbException("优惠券核销失败:目标更新条数：" + couponCountMap.size() + "实际更新条数：" + num);
        }
    }
}
